<?php

/**
 * @package Pico
 * @subpackage PicoTableOfContent
 * @author {notabug,framagit}.org/ohnonot
*/
class PicoTableOfContent extends AbstractPicoPlugin {

   const API_VERSION = 3;

   // only act if this var is set to True
   private $yeah       = False;

   // default settings
   private $depth       = 4;
   private $min_headers = 2;
   private $top_txt     = ' ∆';
   private $top_txt_title     = 'Home';
   private $caption     = "Table of Contents";
   private $template    = "post"; // config to valid twig template to only act on that template - or "all" for all.

   // internal
   private $toc = '';
   private $xpQuery = '';
   private $content;
   private $css= 'PicoTableOfContent.css';
   private $css_alt= '';

   public function onConfigLoaded(array &$config)
   {
      if(isset($config['PicoTableOfContent']['depth']))             $this->depth            = &$config['PicoTableOfContent']['depth'];
      if(isset($config['PicoTableOfContent']['min_headers']))       $this->min_headers      = &$config['PicoTableOfContent']['min_headers'];
      if(isset($config['PicoTableOfContent']['top_txt']))           $this->top_txt          = &$config['PicoTableOfContent']['top_txt'];
      if(isset($config['PicoTableOfContent']['top_txt_title']))     $this->top_txt_title    = &$config['PicoTableOfContent']['top_txt_title'];
      if(isset($config['PicoTableOfContent']['caption']))           $this->caption          = &$config['PicoTableOfContent']['caption'];
      if(isset($config['PicoTableOfContent']['template']))          $this->template         = &$config['PicoTableOfContent']['template'];

      // building the xpath query for all desired headers, e.g. '//h1|//h2|//h3'
      for ($i=1; $i <= $this->depth; $i++) {
        $this->xpQuery = $this->xpQuery.'//h'.$i;
        ( $i < $this->depth ) and $this->xpQuery = $this->xpQuery.'|';
      }
   }
   public function onMetaParsed(array &$meta)
   {
    if(isset($meta['toc_disabled']) && $meta['toc_disabled'] === TRUE) { $this->yeah = False; return; }
    if(isset($meta['toc_css'])) { $this->css_alt = $meta['toc_css']; }
    if($meta['template'] === $this->template || $this->template === "all") $this->yeah = True;
   }

   public function onContentParsed(&$content)
   {
    // only continue if we want TOC for this page
    if($this->yeah != True) return;

    if(trim($content)=="") { $this->yeah = False; return; }

    $dom = new DOMDocument();
    $dom->preserveWhiteSpace = true;
    // encoding problems require us to jump through hoops here...
    // please read this: stackoverflow.com/a/11310258
    // $content is a html fragment. We like to modify its content with DOMDocument, but it prefers complete documents and completes them
    // if necessary. This leads to strangeness. The best thing I found is to give it a complete document with all required headers and tags,
    // then strip those off in the end. That way we DOMDocument doesn't meddle, and we have control. Hah, hopefully.
    $dom->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$content.'</body></html>',
    LIBXML_NONET|LIBXML_COMPACT|LIBXML_HTML_NODEFDTD|LIBXML_NOWARNING);
    //~ LIBXML_NOEMPTYTAG|LIBXML_NOENT|LIBXML_HTML_NOIMPLIED|LIBXML_NOERROR);
    $trim_before=92; // What we need to trim when saving (the above string added to $content)
    $trim_after=-15; //   - " -

    $xp = new DOMXPath($dom);
    $nodes =$xp->query($this->xpQuery);

    // DEBUG
    //~ echo '<pre>';
    //~ foreach ($nodes as $node) {
        //~ echo $node->nodeValue;
    //~ }
    //~ var_dump($nodes);
    //~ array_walk(iterator_to_array($nodes),'var_dump');
    //~ echo '</pre>';

    if($nodes->length < $this->min_headers)
     { $this->yeah = False; return; }

    // add id's to the h tags and at the same time build the TOC
    foreach($nodes as $i => $sort)
    {
      if (isset($sort->tagName) && $sort->tagName !== '')
      {
         if($sort->getAttribute('id') === "")
         {
            $text = preg_replace('~[^\\pL0-9_]+~u', '-', $sort->nodeValue);
            $text = trim($text,'-');
            $text = strtolower($text);

            // build TOC before manipulatng the nodes
            $this->toc = $this->toc.'<li class="toc'.substr($sort->nodeName, 1).'"><a href="#'.$text.'">'.$sort->nodeValue.'</a></li>';

            $sort->setAttribute('id',$text);
            $a = $dom->createElement('a', $this->top_txt);
            $a->setAttribute('title', $this->top_txt_title);
            $a->setAttribute('href', '#');
            $a->setAttribute('class', 'toc-nav');
            $sort->appendChild($a);
         }
      }
    }
    // we now have a full html document in $dom. Let's extract only what is between
    // <body> tags,as it was before. - stackoverflow.com/a/18090774
    //~ $content='';
    //~ foreach($dom->getElementsByTagName("body")->item(0)->childNodes as $child) {
        //~ $content .= $dom->saveHTML($child);
    //~ }
    $content = $dom->saveHTML();
    // remove the tag we added at loadHTML
    $content = substr($content,$trim_before,$trim_after);
    //~ // please read this: stackoverflow.com/a/20675396
    //~ $content = $dom->saveHTML($dom->documentElement);
    //~ $len = strlen($utf8); if(substr($content,0,$len) === $utf8) $content = substr($content,$len);

    $cap = $this->caption =='' ? "" :  '<p id="toc-header">'.$this->caption.'</p>';
    $this->toc = '<div id="toc">'.$cap.'<ul>'.$this->toc.'</ul></div>';
   }

   public function onPageRendering(&$templateName, array &$twigVariables)
   {
    if($this->yeah != True) return;
    // adding the correct stylesheet
    if($this->css_alt != "" and substr($this->css_alt,0,1) === '/') $this->css=$twigVariables['base_url'].$this->css_alt;
        //~ else { 
            //~ $test=$twigVariables['theme_url'].'/'.$this->css_alt;
            //~ if(file_exists($_SERVER['DOCUMENT_ROOT'].$test)) $this->css=$test;
            //~ else $this->css=$twigVariables['assets_url'].'/'.$this->css_alt;
        //~ }
    else {
        $test=$twigVariables['assets_url'].'/'.$this->css;
        if(file_exists($_SERVER['DOCUMENT_ROOT'].$test)) $this->css=$test;
        else $this->css=$twigVariables['plugins_url'].'/PicoTableOfContent/'.$this->css;
    }
    $this->toc='<link rel="stylesheet" href="'.$this->css.'" type="text/css" />'.$this->toc;
    $twigVariables['TableOfContent'] = new Twig_Markup($this->toc, 'UTF-8'); // tells twig to render this as raw html
   }
}
